#!/usr/bin/python

# In Ubuntu, use the following command to install rbtools
# $ apt-get install python-setuptools
# $ easy_install -U RBTools
# Reference Doc: 
# http://www.reviewboard.org/docs/manual/dev/webapi/2.0/resources/root/#webapi2.0-root-resource
# Author: Lance Zhao (dahai.zhao@hp.com)
# Date: 2013-08-01
import sys
from rbtools.api.client import RBClient
from datetime import datetime, date, time

RB_TM_FMT = "%Y-%m-%d %H:%M:%S"
DEBUG = True
MAX_RST = 1000


# (in YYYY-MM-DD HH:MM:SS format).
def sec_btw( prevs, later ):
  prevs_tm = datetime.strptime( prevs,  RB_TM_FMT )
  later_tm = datetime.strptime( later,  RB_TM_FMT )
  secdelta = later_tm - prevs_tm
  return secdelta.total_seconds()

class Sprint():
  def __init__(self):
    self.sprint_start_time={\
     17: datetime.strptime( "2014-01-06 00:00:00",  RB_TM_FMT ),\
     16: datetime.strptime( "2013-12-02 00:00:00",  RB_TM_FMT ),\
     15: datetime.strptime( "2013-11-18 00:00:00",  RB_TM_FMT ),\
     14: datetime.strptime( "2013-10-21 00:00:00",  RB_TM_FMT ),\
     13: datetime.strptime( "2013-09-16 00:00:00",  RB_TM_FMT ),\
     12: datetime.strptime( "2013-08-12 00:00:00",  RB_TM_FMT ),\
     11: datetime.strptime( "2013-07-15 00:00:00",  RB_TM_FMT ),\
     10: datetime.strptime( "2013-06-08 00:00:00",  RB_TM_FMT ),\
     9 : datetime.strptime( "2013-05-06 00:00:00",  RB_TM_FMT ),\
     8 : datetime.strptime( "2013-03-25 00:00:00",  RB_TM_FMT ),\
     7 : datetime.strptime( "2013-02-20 00:00:00",  RB_TM_FMT ),\
     6 : datetime.strptime( "2013-01-28 00:00:00",  RB_TM_FMT ),\
     5 : datetime.strptime( "2012-12-24 00:00:00",  RB_TM_FMT ),\
     4 : datetime.strptime( "2012-11-20 00:00:00",  RB_TM_FMT ),\
     3 : datetime.strptime( "2012-10-17 00:00:00",  RB_TM_FMT ),\
     2 : datetime.strptime( "2012-09-10 00:00:00",  RB_TM_FMT ),\
     1 : datetime.strptime( "2012-08-06 00:00:00",  RB_TM_FMT ),\
     0 : datetime.strptime( "2012-06-04 00:00:00",  RB_TM_FMT )\
      }

    self.sprint_end_time={\
     17: datetime.strptime( "2014-01-24 23:00:00",  RB_TM_FMT ),\
     16: datetime.strptime( "2013-12-31 23:00:00",  RB_TM_FMT ),\
     15: datetime.strptime( "2013-12-03 23:00:00",  RB_TM_FMT ),\
     14: datetime.strptime( "2013-11-15 23:00:00",  RB_TM_FMT ),\
     13: datetime.strptime( "2013-10-17 23:00:00",  RB_TM_FMT ),\
     12: datetime.strptime( "2013-09-06 23:00:00",  RB_TM_FMT ),\
     11: datetime.strptime( "2013-08-09 23:00:00",  RB_TM_FMT ),\
     10: datetime.strptime( "2013-07-10 23:00:00",  RB_TM_FMT ),\
     9 : datetime.strptime( "2013-06-04 23:00:00",  RB_TM_FMT ),\
     8 : datetime.strptime( "2013-04-24 23:00:00",  RB_TM_FMT ),\
     7 : datetime.strptime( "2013-03-21 23:00:00",  RB_TM_FMT ),\
     6 : datetime.strptime( "2013-02-08 23:00:00",  RB_TM_FMT ),\
     5 : datetime.strptime( "2013-01-24 23:00:00",  RB_TM_FMT ),\
     4 : datetime.strptime( "2012-12-19 23:00:00",  RB_TM_FMT ),\
     3 : datetime.strptime( "2012-11-16 23:00:00",  RB_TM_FMT ),\
     2 : datetime.strptime( "2012-09-29 23:00:00",  RB_TM_FMT ),\
     1 : datetime.strptime( "2012-09-07 23:00:00",  RB_TM_FMT ),\
     0 : datetime.strptime( "2012-08-03 23:00:00",  RB_TM_FMT )\
      }

  def get_sprint_num(self, check_time_str):
    cktm = datetime.strptime( check_time_str,  RB_TM_FMT )
    for (spt, stm) in self.sprint_start_time.items():
      if ( cktm >= stm and cktm <= self.sprint_end_time[spt] ):
        return spt
    return -1
    
  def is_in_sprint(self, check_time_str, sprint_num):
    cktm = datetime.strptime( check_time_str,  RB_TM_FMT )
    if ( sprint_num in self.sprint_start_time.keys() ):
      if ( cktm >= self.sprint_start_time[sprint_num] and \
           cktm <= self.sprint_end_time[sprint_num] ):
        return True
    return False

SPRINT = Sprint()
    
class Developer_Statics(object):
  def __init__(self, usr):
    self.username = usr.username
    self.email = usr.email
    
    self.list_requests = []
    self.list_changes = []
    self.list_reviews = []
    self.list_comments = []
    self.list_rev_replies = []
    self.list_cmt_replies = []

    self.dict_review_request = {}
    self.dict_revrep_review = {}
    
    # reviewer shipt it reviews / reviewer total reviews count
    self.cnt_shipit = 0
    # reviewer bug comments (open an issue state)
    self.cnt_bug_catch = 0
    
    # Sum of all review response time / review count
    self.avtm_review = 999.9  # in hours
    # Sum of all review reply response time / review reply count
    self.avtm_review_reply = 999.9  # in hours
    
    # replied requests / total requests in RB
    self.pct_reviewed_request = 0.0
    # submitter replied reviews / (total reivews to submiter / ship it reviews )
    self.pct_replied_review = 0.0
    self.pct_replied_comment = 0.0


  def get_review_respond_time(self, rev):
    if ( self.dict_review_request.has_key(rev) ):
      req = self.dict_review_request[rev]
      respond_time_list = []

      diff = sec_btw(req.time_added, rev.timestamp)
      if ( diff > 0 ):
        respond_time_list.append( diff )

      for chg in req.get_changes( ):
        diff = sec_btw(chg.timestamp, rev.timestamp)
        if ( diff > 0 ):
          respond_time_list.append( diff )

      if (len(respond_time_list) > 0):
        # Use the minial interval
        respond_time_list.sort()
        return respond_time_list[0]
    return 0
      
  def calc_avtm_review(self):
    total_sec = 0
    review_cnt = 0
    for rev in self.list_reviews:
      rev_rsp_tm = self.get_review_respond_time(rev)
      if ( rev_rsp_tm > 0 ):
        total_sec = total_sec + rev_rsp_tm
        review_cnt = review_cnt + 1
    if ( review_cnt > 0 ):
      self.avtm_review = (total_sec/review_cnt)/3600.0
      if (DEBUG):
        print "{%s} avtm_review = %3.1f hours, for [%d/%d] calc reviews"%( self.username, self.avtm_review, review_cnt, len(self.list_reviews) )
    return True
  
  def calc_avtm_review_reply(self):
    total_sec = 0
    revrep_cnt = 0
    for revrep in self.list_rev_replies:
      if ( not self.dict_revrep_review.has_key( revrep ) ):
        continue
      rev = self.dict_revrep_review[revrep]
      revrep_rsp_tm = sec_btw(rev.timestamp, revrep.timestamp)
      if ( revrep_rsp_tm > 0 ):
        total_sec = total_sec + revrep_rsp_tm
        revrep_cnt = revrep_cnt + 1
    if ( revrep_cnt > 0 ):
      self.avtm_review_reply = (total_sec/revrep_cnt)/3600.0
      if (DEBUG):
        print "{%s} avtm_review_reply = %3.1f hours, for [%d/%d] calc review replies"%( self.username, self.avtm_review_reply, revrep_cnt, len(self.list_rev_replies) )
    return True
  
  def calc_pct_replied_review(self):
    total_got_review_cnt = 0
    for req in self.list_requests:
      for rev in req.get_reviews( max_results=MAX_RST ):
        if ( not rev.ship_it ):
          total_got_review_cnt = total_got_review_cnt + 1
    total_replied_review = len(self.list_rev_replies)
    if ( total_got_review_cnt > 0):
      self.pct_replied_review = total_replied_review / (total_got_review_cnt*1.0)
      if (DEBUG):
        print "{%s} pct_replied_review = %3.1f [%d/%d]"%( self.username, self.pct_replied_review, total_replied_review, total_got_review_cnt)
    return True

  def calc_pct_replied_comment(self):
    total_got_comment_cnt = 0
    for req in self.list_requests:
      for rev in req.get_reviews( max_results=MAX_RST ):
        cmts = rev.get_diff_comments( counts_only=True, max_results=1000 )
        total_got_comment_cnt = total_got_comment_cnt + cmts.count
    total_replied_comment = len(self.list_cmt_replies)
    if ( total_got_comment_cnt > 0):
      self.pct_replied_comment = total_replied_comment / (total_got_comment_cnt*1.0)
      if (DEBUG):
        print "{%s} pct_replied_comment = %3.1f [%d/%d]"%( self.username, self.pct_replied_comment, total_replied_comment, total_got_comment_cnt )
    return True
    
  def calc_all(self):
    self.calc_avtm_review()
    self.calc_avtm_review_reply()
    self.calc_pct_replied_review()
    self.calc_pct_replied_comment()
    return True
    
  def print_count(self):
  
    cnt_request = len( self.list_requests )
    cnt_change = len( self.list_changes )
    cnt_review = len( self.list_reviews )
    cnt_comment = len( self.list_comments )
    cnt_review_reply = len( self.list_rev_replies )
    cnt_comment_reply = len( self.list_cmt_replies )
    
    print "%12s, %3d, %3d, %3d, %3d, %3d, %3d, %3d, %3d, %3.1f, %3.1f, %1.3f, %1.3f, %1.3f"%(\
      self.username, cnt_request, cnt_change, cnt_review, self.cnt_shipit, \
      cnt_comment, self.cnt_bug_catch, cnt_review_reply, cnt_comment_reply, \
      self.avtm_review, self.avtm_review_reply, self.pct_reviewed_request, \
      self.pct_replied_review, self.pct_replied_comment )
    sys.stdout.flush()
    return True
  
  @staticmethod
  def print_header():
    print "username, request, changes, review, shipit, comment, found_bugs, review_reply, comment_reply, average_review_time, average_review_reply_time, pct_reviewed_request, pct_replied_review, pct_replied_comment"
    print ""
    print "%12s, %3s, %3s, %3s, %3s, %3s, %3s, %3s, %3s, %5s, %5s, %5s, %5s, %5s"%(\
       'username','req', 'chg', 'rev', 'shp', 'cmt', 'bug', 'rvr', 'cmr', 'rvtm', 'rvptm', 'ptrvq', 'ptrpr', 'ptrpc')
    print "---------------------------------------------------------------------------------"
    return True

class Review_Matrix:
  def __init__(self):
    self.list_developers = []
    self.list_devstat = []
    
    self.list_requests = []
    self.list_reviews = []
    self.list_comments = []
    self.list_rev_replies = []
    self.list_cmt_replies = []
    
    self.dict_change_request = {}
    self.dict_review_request = {}
    self.dict_revrep_review = {}
    # comment reply got a field called "reply_to" point to comment, so no need for that
    # self.dict_cmtrep_comment = {}

  def handle_request(self, req):
    self.list_requests.append( req )
    for devstat in self.list_devstat:
      if ( devstat.username == req.get_submitter().username ):
        devstat.list_requests.append( req )
        break
    if ( DEBUG ):
      print ""
      print "REQ> [%s] {%s} Request#%d"%( \
        req.time_added, req.get_submitter().username, req.id  )
      sys.stdout.flush()
    return
    
  def handle_change(self, chg, req):
    self.dict_change_request[ chg ] = req
    for devstat in self.list_devstat:
      if ( devstat.username == req.get_submitter().username ):
        devstat.list_changes.append( chg )
        break
    if ( DEBUG ):
      print "REQ.CHG> [%s] Change#%d"%(\
        chg.timestamp, chg.id )
      sys.stdout.flush()
    return

  def handle_review(self, rev, req):
    self.list_reviews.append( rev )
    self.dict_review_request[ rev ] = req
    for devstat in self.list_devstat:
      if ( devstat.username == rev.get_user().username ):
        devstat.list_reviews.append( rev )
        devstat.dict_review_request[ rev ] = req
        if ( rev.ship_it ):
          devstat.cnt_shipit = devstat.cnt_shipit + 1
        break
    if ( DEBUG ):
      print "REV> [%s] {%s} Review#%d Ship_it=%s"%( \
        rev.timestamp, rev.get_user().username, rev.id, rev.ship_it)
      sys.stdout.flush()
    return
  
  def handle_review_reply(self, revrep, rev ):
    self.list_rev_replies.append( revrep )
    self.dict_revrep_review[ revrep ] = rev
    for devstat in self.list_devstat:
      if ( devstat.username == revrep.get_user().username ):
        devstat.list_rev_replies.append( revrep )
        devstat.dict_revrep_review[ revrep ] = rev
        break
    if ( DEBUG ):
      print "REV.REP> [%s] {%s} Review_Reply#%d"%( \
        revrep.timestamp, revrep.get_user().username, revrep.id )
      sys.stdout.flush()
    return
  
  def handle_comment(self, cmt):
    self.list_comments.append( cmt )
    for devstat in self.list_devstat:
      if ( devstat.username == cmt.get_user().username ):
        devstat.list_comments.append( cmt )
        if ( cmt.issue_opened ):
          devstat.cnt_bug_catch = devstat.cnt_bug_catch + 1
        break
    if ( DEBUG ):
      print "REV.CMT> [%s] {%s} Comment#%d"%( \
        cmt.timestamp, cmt.get_user().username, cmt.id )
      sys.stdout.flush()
    return
  
  def handle_comment_reply(self, cmtrep):
    self.list_cmt_replies.append( cmtrep )
    for devstat in self.list_devstat:
      if ( devstat.username == cmtrep.get_user().username ):
        devstat.list_cmt_replies.append( cmtrep )
        break
    if ( DEBUG ):
      print "REV.CMT.REP> [%s] {%s} Comment_Reply#%d"%( \
        cmtrep.timestamp, cmtrep.get_user().username, cmtrep.id )
      sys.stdout.flush()
    return

  def cache_web(self, rb_url, sprint_num=None):
    self.client = RBClient( rb_url )
    self.root  = self.client.get_root()
    for usr in self.root.get_users(max_results=50):
      self.list_developers.append( usr )
      self.list_devstat.append( Developer_Statics( usr ) )

    for req in self.root.get_review_requests(max_results=MAX_RST, status="all"):

      if ( sprint_num is not None and \
           not SPRINT.is_in_sprint( req.time_added, sprint_num ) ):
        if ( DEBUG ):
          print ""
          print "REQ>X [%s] {%s} Request#%d, Skipped: not in Sprint-%d"%( \
          req.time_added, req.get_submitter().username, req.id, sprint_num)
          sys.stdout.flush()
        continue

      self.handle_request( req )
      for chg in req.get_changes(max_results=MAX_RST):
        self.handle_change( chg, req)

      for rev in req.get_reviews(max_results=MAX_RST):
        self.handle_review( rev, req )

        for cmt in rev.get_diff_comments(max_results=MAX_RST):
          self.handle_comment( cmt )

        for revrep in rev.get_replies(max_results=MAX_RST):
          self.handle_review_reply ( revrep, rev )
          for cmtrep in revrep.get_diff_comments(max_results=MAX_RST):
            self.handle_comment_reply( cmtrep )
    return True
  
  def calc_data(self):
    for devstat in self.list_devstat:
      devstat.calc_all()
      if ( self.list_requests ):
        devstat.pct_reviewed_request = len(set(devstat.dict_review_request.values()))/(1.0*len(self.list_requests))
    return True
    
  def print_data(self):
    Developer_Statics.print_header()
    for devstat in self.list_devstat:
      devstat.print_count()
    return True
      
if __name__=="__main__":
  rmx = Review_Matrix()
  rmx.cache_web("http://linuxqa.chn.hp.com:8123/", 13)
  rmx.calc_data()
  rmx.print_data()
